<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    /* 
深度克隆
1). 大众乞丐版
    问题1: 函数属性会丢失
    问题2: 循环引用会出错
2). 面试基础版本
    解决问题1: 函数属性还没丢失
3). 面试加强版本
    解决问题2: 循环引用正常
4). 面试加强版本2(优化遍历性能)
    数组: while | for | forEach() 优于 for-in | keys()&forEach() 
    对象: for-in 与 keys()&forEach() 差不多
*/

    const obj = {
      a: {

      },
      b: [],
      fn: function () {}
    }
    obj.a.c = obj.b
    obj.b[0] = obj.a
    /* 
    1). 大众乞丐版
      问题1: 函数属性会丢失
      问题2: 循环引用会出错
    */
    function deepClone1(target) {
      return JSON.parse(JSON.stringify(target))
    }

    

    /* 
    获取数据的类型字符串名
    */
    function getType(data) {
      // data.toString()
      return Object.prototype.toString.call(data).slice(8, -1) // -1代表最后一位
      // [object Array]  ===> Array  [object Object] ==> Object
    }

    /*
    2). 面试基础版本
      解决问题1: 函数属性还没丢失
    */
    function deepClone2(target) {
      const type = getType(target)

      if (type === 'Object' || type === 'Array') {
        const cloneTarget = type === 'Array' ? [] : {}
        for (const key in target) {
          if (target.hasOwnProperty(key)) {
            cloneTarget[key] = deepClone2(target[key])
          }
        }
        return cloneTarget
      } else {
        return target
      }
    }

    /* 
    3). 面试加强版本
      解决问题2: 循环引用正常
    */
    function deepClone3(target, map=new Map()) {
      const type = getType(target)

      if (type === 'Object' || type === 'Array') {
        // 在准备创建clone对象前, 优先考虑缓存中的
        let cloneTarget = map.get(target)
        if (cloneTarget) {
          return cloneTarget
        }
        // 缓存中没有, 创建新的clone对象
        cloneTarget = type === 'Array' ? [] : {}
        // 一旦产生一人新的clone对象, 就缓存到map中
        map.set(target, cloneTarget)
        for (const key in target) {
          if (target.hasOwnProperty(key)) {
            cloneTarget[key] = deepClone3(target[key], map)
          }
        }
        return cloneTarget
      } else {
        return target
      }
    }

    const obj2 = deepClone3(obj)
    console.log(obj2, obj2.a===obj.a, obj2.fn===obj.fn)

    /* 
    4). 面试加强版本2(优化遍历性能)
        数组: while | for | forEach() 优于 for-in | keys()&forEach() 
        对象: for-in 与 keys()&forEach() 差不多
    */
    function deepClone4(target, map = new Map()) {
      const type = getType(target)
      if (type === 'Object' || type === 'Array') {
        let cloneTarget = map.get(target)
        if (cloneTarget) {
          return cloneTarget
        }

        if (type === 'Array') {
          cloneTarget = []
          map.set(target, cloneTarget)
          target.forEach((item, index) => {
            cloneTarget[index] = deepClone4(item, map)
          })
        } else {
          cloneTarget = {}
          map.set(target, cloneTarget)
          Object.keys(target).forEach(key => {
            cloneTarget[key] = deepClone4(target[key], map)
          })
        }

        return cloneTarget
      } else {
        return target
      }
    }
  </script>
</body>

</html>